package com.copractice.bl.question;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.hibernate.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.copractice.bl.BLConstants;
import com.copractice.bl.InitialParameters;
import com.copractice.data.dao.hibernate.util.DBUtils;
import com.copractice.data.dao.question.QuestionDAO;
import com.copractice.data.dao.tag.TagDAO;
import com.copractice.data.dao.user.UserDAO;
import com.copractice.data.entity.answer.Answer;
import com.copractice.data.entity.feedback.FeedbackAnswer;
import com.copractice.data.entity.feedback.UsersFeedback;
import com.copractice.data.entity.question.Question;
import com.copractice.data.entity.question.QuestionType;
import com.copractice.data.entity.tag.Tag;
import com.google.inject.Inject;


/**
 * Contains business logic methods of working with questions.
 * 
 * @author Dmitry Churbanov (dmitry.churbanov@gmail.com)
 * @version 1.0 04/14/2009
 */
public class QuestionBL {
    
    private static final Logger LOG = LoggerFactory.getLogger(QuestionBL.class);

    private QuestionDAO questionDAO;
    private UserDAO userDAO;
    private TagDAO tagDAO;
    
    public QuestionDAO getQuestionDAO() {
        return questionDAO;
    }
    @Inject
    public void setQuestionDAO(QuestionDAO questionDAO) {
        this.questionDAO = questionDAO;
    }
    public UserDAO getUserDAO() {
        return userDAO;
    }
    @Inject
    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }
    public TagDAO getTagDAO() {
        return tagDAO;
    }
    @Inject
    public void setTagDAO(TagDAO tagDAO) {
        this.tagDAO = tagDAO;
    }
    
    /**
     * Finds question by its id value.
     * 
     * @param questionId id of a question
     * @return the <code>edu.vt.intelligenttutor.data.entity.question.Question</code> objects
     *         with the same id as a parameter value, if such question can not be found null
     *         is returned
     */
    public Question findQuestion(Long questionId) {
        return questionDAO.getById(questionId);
    } 
    
    /**
     * Adds new question.
     * 
     * @param question question to add
     * @param userId id of a user who is creating the question
     */
    public boolean addQuestion(QuestionToSave question, Long userId) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Add new question : {}", question);
            LOG.debug("Add new question.difficulty : {}", question.getDifficulty());
        }
        
        Transaction tx = null;
        
        try {
            
            tx = DBUtils.beginTransaction();
            Question saveQuestion = new Question();
            saveQuestion.setAnswers(makeAnswers(question.getAnswers(), question.getRightAnswer(), saveQuestion));
            saveQuestion.setFeedback(makeFeedback(question.getDefaultHint(), userId, saveQuestion));
            saveQuestion.setCondition(question.getQuestionText());
            saveQuestion.setCreated(userDAO.getById(userId));
            saveQuestion.setCreatedDate(new Date());
            saveQuestion.setNumberOfUserAttempts(0L);
            saveQuestion.setNumberOfUserRating(1L);
            saveQuestion.setIntelDiff(0L);
            saveQuestion.setUserDiff(convertDifficulty(question.getDifficulty()));
            saveQuestion.setDiscrimination(1D); 
            saveQuestion.setType(QuestionType.SINGLE_CHOISE);
            saveQuestion.setTags(getQuestionTags(question.getCategoryId(),
                                                 question.getNewCategoryName()));
            
            if (LOG.isDebugEnabled()) {
                LOG.debug("Save question : " + saveQuestion);
            }
            questionDAO.save(saveQuestion);
            tx.commit();
        } catch(RuntimeException ex) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Rollback the transaction");
            }
            tx.rollback();
            if (LOG.isDebugEnabled()) {
                throw new RuntimeException(ex);
            }
        }
        
        return true;
    }    
    
    /**
     * Creates list of answers for the new question
     * 
     * @param incorrectAnswers list of incorrect answers
     * @param rightAnswer text of a correct answer
     * @param question question to which new feedback will belong
     * 
     * @return the <code>list</code> of incorrect answers
     */
    private List<Answer> makeAnswers(List<String> incorrectAnswers, String rightAnswer, Question question) {
        List<Answer> answers = new ArrayList<Answer>();
        
        Answer answer = new Answer(rightAnswer, true);
        answer.setQuestion(question);
        answers.add(answer);
        
        for (String incorrect : incorrectAnswers) {
            answer = new Answer(incorrect, false);
            answer.setQuestion(question);
            answers.add(answer);
        }
        
        if (LOG.isDebugEnabled()) {
            LOG.debug("List of answers was created: {}", answers);
        }
        return answers;
    }
    
    /**
     * Creates list of feedbacks for the new question.
     *  
     * @param feedbackText text of a feedback
     * @param userId id of a user who created the feedback
     * @param question question to which new feedback will belong
     * 
     * @return list of feedbacks for the new question
     */
    private List<UsersFeedback> makeFeedback(String feedbackText, Long userId, Question question) {
        
        ArrayList<UsersFeedback> feedbackList = new ArrayList<UsersFeedback>();
        UsersFeedback feedback = new UsersFeedback();
        feedback.setFeedback(feedbackText);
        feedback.setCreatedUser(userDAO.getById(userId));
        feedback.setQuestion(question);
        feedback.setViewers(0L);
        feedback.setFeedbackAnswer(makeFeedBackAnswers(feedback, question.getAnswers()));
        
        feedbackList.add(feedback);
        
        if (LOG.isDebugEnabled()) {
            LOG.debug("List of UsersFeedback : " + feedbackList);
        }
        
        return feedbackList;
    }   
    
    /**
     * Returns list of tags which can be assigned to a question.
     * List of tags is created by using <code>tagId</code> if it is not null or <code>newCategoryName</code> parameter. 
     * If tag with the name value of <code>newCategoryName</code> parameter does not exists new tag will be created;
     * 
     * @param tagId id of a current tag
     * @param newCategoryName tag name if new tag name should be created;
     * 
     * @return list of <code>Tag</code>s which are created from <code>tagId</code> and <code>newCategoryName</code> 
     *         parameters 
     */
    private List<Tag> getQuestionTags(Long tagId, String newCategoryName) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("tagId = {}; newCategoryName = {}", tagId, newCategoryName);
        }
        List<Tag> tags = new ArrayList<Tag>();
        
        if (tagId != null) {
            tags.add(tagDAO.getById(tagId));
            if (LOG.isDebugEnabled()) {
                LOG.debug("Tag with tagId = '{}' was added", tagId);
            }
        } else {
            Tag tag = tagDAO.findByName(newCategoryName);
            if (tag == null) {
                tag = new Tag();
                tag.setName(newCategoryName);
                tagDAO.save(tag);
                if (LOG.isDebugEnabled()) {
                    LOG.debug("New tag: '{}' was created", tag);
                }
            }
            tags.add(tag);
        }
        
        return tags;
    }    
    
    /**
     * Creates list of <code>FeedbackAnswer</code>s for the feedback and the list 
     * of <code>Answer</code>s. 
     * 
     * @param feedback <code>UsersFeedback</code> for which list of <code>FeedbackAnswer</code>s will be created
     * @param answers list of <code>Answer</code>s for which list of <code>FeedbackAnswer</code>s will be created
     *        
     * @return list of <code>FeedbackAnswer</code> objects
     */
    private List<FeedbackAnswer> makeFeedBackAnswers(UsersFeedback feedback, List<Answer> answers) {
        List<FeedbackAnswer> feedbackAnswers = new ArrayList<FeedbackAnswer>();
        
        for(Answer answer : answers) {
            FeedbackAnswer feedbackAnswer = new FeedbackAnswer();
            feedbackAnswer.setAnswer(answer);
            feedbackAnswer.setFeedback(feedback);
            feedbackAnswer.setHelpfulness(InitialParameters.FEEDBACK_HELPFULNESS);
            feedbackAnswer.setRating(InitialParameters.FEEDBACK_RATING);
            feedbackAnswer.setNumberOfHelpfulness(InitialParameters.NUMBER_OF_HELPFULNESS);
            feedbackAnswer.setNumberOfUserRating(InitialParameters.NUMBER_OF_USER_RATING);
            
            if (LOG.isDebugEnabled()) {
                LOG.debug("FeedbackAnswer is created : {}", feedbackAnswer);
            }
            feedbackAnswers.add(feedbackAnswer);
        }
        
        return feedbackAnswers;
    }    
    
    /**
     * Creates list of feedbacks for the question.
     * 
     * @param feedbackText text of a feedback
     * @param userId id of a user who created the feedback
     * @param questionId id of a question to which new feedback will belong
     * @return
     */
    public UsersFeedback makeFeedback(String feedbackText, Long userId, Long questionId) {
        Question question = findQuestion(questionId);
        List<UsersFeedback> feedbacks = makeFeedback(feedbackText, userId, question);
        return feedbacks.get(0);
    }    
    
   
    private Long convertDifficulty(Long value) {
        if (value == 0) {
            return BLConstants.DEFAULT_DIFFICULTY_FACTOR;
        } else {
            return BLConstants.DIFFICULTY_FACTOR * value;
        }
    }     
    
}
